/*
 * Merge.cpp
 *
 *  Created on: 23/10/2011
 *      Author: diego
 */

#include "Merge.h"

Merge::Merge() {

}

// lista tiene la lista de prefijos de los subindices a mergear, vias es el tamaño de la lista
// me retorna el prefijo del indice que genero
string Merge::mergeIndices(Lista<string>* lista, int vias, int nro_pasada){
	cout << ".";
	IteradorLista<string> ilista = lista->iterador();
	int cont_lista = 0;
	while (ilista.tieneSiguiente()){
		ilista.siguiente();
		cont_lista++;
	}

	if (cont_lista < vias){
		vias = cont_lista;
	}

	IteradorIndice** entrada = new IteradorIndice*[vias];
	int i;

	super_reg** registros = new super_reg*[vias];

	IndexWriter* salida = new IndexWriter("tmp", 8);

	bool* eof = new bool[vias];

	for (i = 0; i < vias; i++){
		eof[i] = false;
	}


	IteradorLista<string> it = lista->iterador();
	string prefijo;
	i = 0;
	while (it.tieneSiguiente() && i < vias){
		prefijo = it.siguiente();
		entrada[i] = new IteradorIndice(prefijo);
		i++;
	}

	// hago al lectura inicial del primer registro de cada indice por que sino falla por
	// el caracter invisible que no se cual es todavia
	for (i = 0; i < vias; i++){
		if (entrada[i]->tieneSiguiente()){
			registros[i] = entrada[i]->proximoRegistro();
		}
	}

	// 1. buscar el termino menor
	// 2. agarrar los registros que tienen ese termino y mergearlos en 1 solo
	// 3. escribir ese registro
	// 4. leer de los archivos que tenian ese termino
	// 5. volver a 1

	string termino;
	string termino_anterior = "#";//es un caracter invalido de parseo
	super_reg* reg = NULL;
	int cont = 0;

	while (this->quedanRegistros(entrada, vias, eof)){

		this->leerRegistros(entrada, vias, registros, eof, termino);

		termino = this->obtenerMenorTermino(registros, termino_anterior, vias, eof);

		// DEBUG
		//cout << termino << endl;
		reg = this->mergearRegistros(registros, termino, vias, eof);

		salida->write(reg);
		this->updateEOF(eof, entrada, vias, termino, registros);

		termino_anterior = termino;

		cont++;

		this->borrar_registro(reg);
		reg = NULL;
	}

	// cierro las entradas
	for (i = 0; i < vias; i++){
		delete entrada[i];
	}

	for (i = 0; i < vias; i++){
		delete registros[i];
	}
	delete registros;

	delete entrada;

	salida->close();
	delete salida;

	IteradorLista<string> it_lista = lista->iterador();
	Configuracion* conf = Configuracion::getConf(PATH_CONF);
	string pref, path;
	i = 0;
	while (it_lista.tieneSiguiente() && i < vias){
		pref = it_lista.siguiente();

		path = conf->path_indice() + pref + "_indice.dat";
		remove(path.c_str());

		path = conf->path_indice() + pref + "_terminos.dat";
		remove(path.c_str());

		path = conf->path_indice() + pref + "_docs.dat";
		remove(path.c_str());

		path = conf->path_indice() + pref + "_header.dat";
		remove(path.c_str());

		i++;
	}

	string old_s, new_s;

	old_s = conf->path_indice() + "tmp_indice.dat";
	new_s = conf->path_indice() + this->toString(nro_pasada) + "_indice.dat";
	rename(old_s.c_str(), new_s.c_str());

	old_s = conf->path_indice() + "tmp_terminos.dat";
	new_s = conf->path_indice() + this->toString(nro_pasada) + "_terminos.dat";
	rename(old_s.c_str(), new_s.c_str());

	old_s = conf->path_indice() + "tmp_docs.dat";
	new_s = conf->path_indice() + this->toString(nro_pasada) + "_docs.dat";
	rename(old_s.c_str(), new_s.c_str());

	old_s = conf->path_indice() + "tmp_header.dat";
	new_s = conf->path_indice() + this->toString(nro_pasada) + "_header.dat";
	rename(old_s.c_str(), new_s.c_str());

	delete eof;

	return prefijo;
}

void Merge::borrar_registro(super_reg* reg){

	IteradorLista<reg_docs_variable*> it = reg->lista->iterador();
	while (it.tieneSiguiente()){
		delete it.siguiente();
	}

	delete reg->lista;
	delete reg;
}

super_reg* Merge::mergearRegistros(super_reg** registros, string termino, int vias,bool* eof){
	super_reg* reg = new super_reg;

	reg->termino = termino;
	reg->frec_t = 0;
	int i;

	// sumo las frec totales para los registros que tienen el mismo termino
	for (i = 0; i < vias; i++){
		if (registros[i]->termino == termino){
			reg->frec_t += registros[i]->frec_t;
		}
	}

	// hago el merge de las listas de documentos
	reg->lista = new Lista<reg_docs_variable*>();
	reg_docs_variable* reg_lista;
	reg_docs_variable* reg_lista_nuevo;
	reg_docs_variable* reg_lista_anterior = NULL;
	int cont = 0;

	for (i = 0; i < vias; i++){
		if (registros[i]->termino == termino){
			IteradorLista<reg_docs_variable*> it = registros[i]->lista->iterador();
			while (it.tieneSiguiente()){
				reg_lista = it.siguiente();

				reg_lista_nuevo = new reg_docs_variable;
				reg_lista_nuevo->doc_id = reg_lista->doc_id;
				reg_lista_nuevo->frec = reg_lista->frec;

				if (reg_lista_anterior != NULL){
					if (reg_lista_anterior->doc_id == reg_lista_nuevo->doc_id){
						reg_lista_anterior->frec += reg_lista_nuevo->frec;
					}else{
						reg->lista->agregar(reg_lista_nuevo);
						cont++;

						reg_lista_anterior = reg_lista_nuevo;
					}
				}else{
					reg->lista->agregar(reg_lista_nuevo);
					cont++;

					reg_lista_anterior = reg_lista_nuevo;
				}


			}
		}
	}

	reg->cant_docs = cont;

	return reg;
}

string Merge::obtenerMenorTermino(super_reg** registros, string termino_anterior, int vias, bool* eof){
	string inicial = "__termino_inicial__";
	string termino = "__termino_inicial__";

	int i;
	for (i = 0; i < vias; i++){
		if (termino == inicial && registros[i]->termino > termino_anterior){
			termino = registros[i]->termino;
		}else{
			if ((termino > registros[i]->termino && registros[i]->termino > termino_anterior)){
				termino = registros[i]->termino;
			}
		}
	}

	return termino;
}

void Merge::leerRegistros(IteradorIndice** entradas, int vias, super_reg** registros, bool* eof, string termino){
	int i;
	for (i = 0; i < vias; i++){
		if (registros[i]->termino == termino && entradas[i]->tieneSiguiente()){
			this->borrar_registro(registros[i]);
			registros[i] = NULL;

			registros[i] = entradas[i]->proximoRegistro();
		}
	}

	return;
}

void Merge::updateEOF(bool* eof, IteradorIndice** entradas, int vias, string termino, super_reg** registros){
	int i;
	for (i = 0; i < vias; i++){
		if (!entradas[i]->tieneSiguiente() && termino == registros[i]->termino){
			eof[i] = true;
		}
	}
}

bool Merge::quedanRegistros(IteradorIndice** entrada, int vias, bool* eof){
	int i;
	for (i = 0; i < vias; i++){
		if (eof[i] == false){
			return true;
		}
	}

	return false;
}

// falta ver sii ya hay un indice antes generado para no pisarlo al renombrar el ultimo indice y en la ultima pasada
// sacar stopwords!!!
void Merge::mergeIndices_Nvias(int cant_indices){
	cout << "Mergeando sub-indices." << flush;

	// primero checkeo si ya hay algun indice generado anteriormente
	if (this->existeIndice()){
		this->renombrarIndiceExistente(cant_indices);
		cant_indices++;
	}


	if (cant_indices == 1){
		// solo renombro los archivos, tendria que hacerles una pasada para sacar stopwords!!!
		this->renombrarIndiceFinal();
		cout << endl;
		return;
	}

	int i;

	// uso 5 vias como maximo
	int vias = 100;
	if (cant_indices < vias){
		vias = cant_indices;
	}

	int pasadas, cont_pasadas;

	while (cant_indices > 1){

		//Lista<string>* salida = new Lista<string>();

		pasadas = this->cantPasadas(cant_indices, vias);
		cont_pasadas = 0; // me cuenta las pasadas que voy haciendo

		while (cont_pasadas < pasadas){
			//voy guardando los prefijos de los archivos en las listas
			Lista<string>* entrada = new Lista<string>();


			for (i = 1; i <= vias && ((cont_pasadas * vias) + i) <= cant_indices; i++){
				entrada->agregar(this->toString((cont_pasadas * vias) + i));
			}

			/*
			 * ACLARACION 1:
			 * en mergeIndices lo que tiene que hacer es el merge en un archivo con nombre temporal
			 * (ej: tmp_indice.dat, tmp_terminos.dat, tmp_docs.dat), borrar los archivos que mergeo
			 * y renombrar el indice del merge con el prefijo cont_pasadas + 1
			 *
			 * ACLARACION 2:
			 * La lista entrada la puedo remplazar por el prefijo de inicio y el prefijo de fin ya que
			 * siempre estoy haciendo merges de archivos con prefijos consecutivos
			 *
			 */
			this->mergeIndices(entrada, vias, cont_pasadas + 1);

			delete entrada;


			cont_pasadas++;
		}

		cant_indices = cont_pasadas;
	}

	this->renombrarIndiceFinal();

	cout << endl;

	return;
}

bool Merge::existeIndice(){
	ifstream ifile("indice/indice.dat");
	return ifile;
}

void Merge::renombrarIndiceExistente(int cant_indices){
	int i;
	string old_path, new_path;
	for (i = cant_indices; i > 0; i--){
		old_path = "indice/" + this->toString(i) + "_indice.dat";
		new_path = "indice/" + this->toString(i+1) + "_indice.dat";
		rename(old_path.c_str(), new_path.c_str());

		old_path = "indice/" + this->toString(i) + "_terminos.dat";
		new_path = "indice/" + this->toString(i+1) + "_terminos.dat";
		rename(old_path.c_str(), new_path.c_str());

		old_path = "indice/" + this->toString(i) + "_docs.dat";
		new_path = "indice/" + this->toString(i+1) + "_docs.dat";
		rename(old_path.c_str(), new_path.c_str());

		old_path = "indice/" + this->toString(i) + "_header.dat";
		new_path = "indice/" + this->toString(i+1) + "_header.dat";
		rename(old_path.c_str(), new_path.c_str());
	}

	rename("indice/indice.dat", "indice/1_indice.dat");
	rename("indice/terminos.dat", "indice/1_terminos.dat");
	rename("indice/docs.dat", "indice/1_docs.dat");
	rename("indice/header.dat", "indice/1_header.dat");

}

void Merge::renombrarIndiceFinal(){
	rename("indice/1_indice.dat", "indice/indice.dat");
	rename("indice/1_terminos.dat", "indice/terminos.dat");
	rename("indice/1_docs.dat", "indice/docs.dat");
	rename("indice/1_header.dat", "indice/header.dat");
}

void Merge::renombrarIndice(string prefijo_old, string prefijo_new){
	string old_str = "indice/" + prefijo_old + "_indice.dat";
	string new_str = "indice/" + prefijo_new + "_indice.dat";
	rename(old_str.c_str(), new_str.c_str());

	old_str.clear();new_str.clear();
	old_str = "indice/" + prefijo_old + "_terminos.dat";
	new_str = "indice/" + prefijo_new + "_terminos.dat";
	rename(old_str.c_str(), new_str.c_str());

	old_str.clear();new_str.clear();
	old_str = "indice/" + prefijo_old + "_docs.dat";
	new_str = "indice/" + prefijo_new + "_docs.dat";
	rename(old_str.c_str(), new_str.c_str());
}

int Merge::cantPasadas(int cant_indices, int vias){
	if (cant_indices >= vias){
		if (cant_indices % vias == 0){
			return (cant_indices / vias);
		}else{
			return (cant_indices / vias) + 1;
		}
	}else{
		return 1;
	}
}

string Merge::toString(int n) {
	stringstream Num;
	string str;

	Num << n;

	str = Num.str();

	return str;
}

Merge::~Merge() {
}

